package org.dalmatian.common.excel.utils;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.resource.ClassPathResource;
import cn.hutool.core.util.IdUtil;
import org.dalmatian.common.core.utils.StringUtils;
import org.dalmatian.common.excel.convert.ExcelBigNumberConvert;
import org.dalmatian.common.excel.core.*;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.fill.FillConfig;
import com.alibaba.excel.write.metadata.fill.FillWrapper;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import org.apache.poi.ss.usermodel.*;
import org.dalmatian.common.excel.core.CellMergeStrategy;
import org.dalmatian.common.excel.core.DefaultExcelListener;
import org.dalmatian.common.excel.core.DropDownOptions;
import org.dalmatian.common.excel.core.ExcelDownHandler;
import org.dalmatian.common.excel.core.ExcelListener;
import org.dalmatian.common.excel.core.ExcelResult;
import org.noear.solon.core.handle.Context;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
 * Excel 工具类（Solon 适配版）
 */
public final class ExcelUtil {

    private ExcelUtil() {
    }

    // --------------------- 导入方法 ---------------------

    /**
     * 同步导入(适用于小数据量)
     *
     * @param is    输入流
     * @param clazz 实体类
     * @return 数据集合
     */
    public static <T> List<T> importExcel(InputStream is, Class<T> clazz) {
        return EasyExcel.read(is).head(clazz).autoCloseStream(false).sheet().doReadSync();
    }

    /**
     * 使用校验监听器 异步导入 同步返回
     *
     * @param is         输入流
     * @param clazz      实体类
     * @param isValidate 是否校验数据
     * @return 导入结果
     */
    public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, boolean isValidate) {
        DefaultExcelListener<T> listener = new DefaultExcelListener<>(isValidate);
        EasyExcel.read(is, clazz, listener).sheet().doRead();
        return listener.getExcelResult();
    }

    /**
     * 使用自定义监听器 异步导入 自定义返回
     *
     * @param is       输入流
     * @param clazz    实体类
     * @param listener 自定义监听器
     * @return 导入结果
     */
    public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, ExcelListener<T> listener) {
        EasyExcel.read(is, clazz, listener).sheet().doRead();
        return listener.getExcelResult();
    }

    // --------------------- 导出方法（Solon适配） ---------------------

    /**
     * 导出Excel到响应
     *
     * @param list      数据列表
     * @param sheetName 工作表名称
     * @param clazz     实体类
     */
    public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz) {
        exportExcel(list, sheetName, clazz, false, null);
    }

    /**
     * 导出Excel到响应（带下拉选项）
     *
     * @param list      数据列表
     * @param sheetName 工作表名称
     * @param clazz     实体类
     * @param options   下拉选项
     */
    public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, List<DropDownOptions> options) {
        exportExcel(list, sheetName, clazz, false, options);
    }

    /**
     * 导出Excel到响应（带合并单元格）
     *
     * @param list      数据列表
     * @param sheetName 工作表名称
     * @param clazz     实体类
     * @param merge     是否合并单元格
     */
    public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, boolean merge) {
        exportExcel(list, sheetName, clazz, merge, null);
    }

    /**
     * 导出Excel到响应（带合并单元格和下拉选项）
     *
     * @param list      数据列表
     * @param sheetName 工作表名称
     * @param clazz     实体类
     * @param merge     是否合并单元格
     * @param options   下拉选项
     */
    public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz,
        boolean merge, List<DropDownOptions> options) {
        Context ctx = Context.current();
        setExcelResponse(sheetName, ctx);
        try (OutputStream os = ctx.outputStream()) {
            internalExportExcel(list, sheetName, clazz, merge, os, options);
        } catch (IOException e) {
            throw new RuntimeException("导出Excel异常", e);
        }
    }

    /**
     * 导出Excel到输出流
     *
     * @param list      数据列表
     * @param sheetName 工作表名称
     * @param clazz     实体类
     * @param os        输出流
     */
    public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, OutputStream os) {
        internalExportExcel(list, sheetName, clazz, false, os, null);
    }

    /**
     * 导出Excel到输出流（带下拉选项）
     *
     * @param list      数据列表
     * @param sheetName 工作表名称
     * @param clazz     实体类
     * @param os        输出流
     * @param options   下拉选项
     */
    public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz,
        OutputStream os, List<DropDownOptions> options) {
        internalExportExcel(list, sheetName, clazz, false, os, options);
    }

    // --------------------- 内部导出方法 ---------------------

    private static <T> void internalExportExcel(List<T> list, String sheetName, Class<T> clazz,
        boolean merge, OutputStream os, List<DropDownOptions> options) {
        ExcelWriterSheetBuilder builder = EasyExcel.write(os, clazz)
            .autoCloseStream(false)
            .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
            .registerConverter(new ExcelBigNumberConvert())
            .sheet(sheetName);

        if (merge) {
            builder.registerWriteHandler(new CellMergeStrategy(list, true));
        }

        HorizontalCellStyleStrategy styleStrategy = new HorizontalCellStyleStrategy(
            getHeadCellStyle(), getDataCellStyle()
        );
        builder.registerWriteHandler(styleStrategy);

        if (CollUtil.isNotEmpty(options)) {
            builder.registerWriteHandler(new ExcelDownHandler(options));
        }

        builder.doWrite(list);
    }

    // --------------------- 样式设置 ---------------------

    private static WriteCellStyle getHeadCellStyle() {
        WriteCellStyle headStyle = new WriteCellStyle();
        headStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex());
        headStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);

        WriteFont headFont = new WriteFont();
        headFont.setFontHeightInPoints((short) 12);
        headFont.setBold(true);
        headStyle.setWriteFont(headFont);

        setBorders(headStyle);
        return headStyle;
    }

    private static WriteCellStyle getDataCellStyle() {
        WriteCellStyle dataStyle = new WriteCellStyle();
        setBorders(dataStyle);
        return dataStyle;
    }

    private static void setBorders(WriteCellStyle style) {
        style.setBorderTop(BorderStyle.THIN);
        style.setBorderBottom(BorderStyle.THIN);
        style.setBorderLeft(BorderStyle.THIN);
        style.setBorderRight(BorderStyle.THIN);
        style.setBottomBorderColor(IndexedColors.BLACK.getIndex());
        style.setTopBorderColor(IndexedColors.BLACK.getIndex());
        style.setLeftBorderColor(IndexedColors.BLACK.getIndex());
        style.setRightBorderColor(IndexedColors.BLACK.getIndex());
    }

    // --------------------- 模板导出（Solon适配） ---------------------

    /**
     * 单表多数据模板导出
     *
     * @param data         数据
     * @param filename     文件名
     * @param templatePath 模板路径
     */
    public static void exportTemplate(List<Object> data, String filename, String templatePath) {
        Context ctx = Context.current();
        setExcelResponse(filename, ctx);
        try (OutputStream os = ctx.outputStream()) {
            internalExportTemplate(data, templatePath, os);
        } catch (IOException e) {
            throw new RuntimeException("导出Excel异常", e);
        }
    }

    /**
     * 多表多数据模板导出
     *
     * @param data         数据
     * @param filename     文件名
     * @param templatePath 模板路径
     */
    public static void exportTemplateMultiList(Map<String, Object> data, String filename, String templatePath) {
        Context ctx = Context.current();
        setExcelResponse(filename, ctx);
        try (OutputStream os = ctx.outputStream()) {
            internalExportTemplateMultiList(data, templatePath, os);
        } catch (IOException e) {
            throw new RuntimeException("导出Excel异常", e);
        }
    }

    /**
     * 多sheet模板导出
     *
     * @param data         数据
     * @param filename     文件名
     * @param templatePath 模板路径
     */
    public static void exportTemplateMultiSheet(List<Map<String, Object>> data, String filename, String templatePath) {
        Context ctx = Context.current();
        setExcelResponse(filename, ctx);
        try (OutputStream os = ctx.outputStream()) {
            internalExportTemplateMultiSheet(data, templatePath, os);
        } catch (IOException e) {
            throw new RuntimeException("导出Excel异常", e);
        }
    }

    // --------------------- 内部模板导出方法 ---------------------

    private static void internalExportTemplate(List<Object> data, String templatePath, OutputStream os) {
        ClassPathResource templateResource = new ClassPathResource(templatePath);
        try (ExcelWriter excelWriter = EasyExcel.write(os)
            .withTemplate(templateResource.getStream())
            .autoCloseStream(true)
            .registerConverter(new ExcelBigNumberConvert())
            .build()) {

            WriteSheet writeSheet = EasyExcel.writerSheet().build();
            for (Object d : data) {
                excelWriter.fill(d, writeSheet);
            }
        }
    }

    private static void internalExportTemplateMultiList(Map<String, Object> data, String templatePath, OutputStream os) {
        ClassPathResource templateResource = new ClassPathResource(templatePath);
        try (ExcelWriter excelWriter = EasyExcel.write(os)
            .withTemplate(templateResource.getStream())
            .autoCloseStream(true)
            .registerConverter(new ExcelBigNumberConvert())
            .build()) {

            WriteSheet writeSheet = EasyExcel.writerSheet().build();
            FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();

            for (Map.Entry<String, Object> entry : data.entrySet()) {
                if (entry.getValue() instanceof Collection) {
                    excelWriter.fill(new FillWrapper(entry.getKey(), (Collection<?>) entry.getValue()),
                        fillConfig, writeSheet);
                } else {
                    excelWriter.fill(entry.getValue(), writeSheet);
                }
            }
        }
    }

    private static void internalExportTemplateMultiSheet(List<Map<String, Object>> data, String templatePath, OutputStream os) {
        ClassPathResource templateResource = new ClassPathResource(templatePath);
        try (ExcelWriter excelWriter = EasyExcel.write(os)
            .withTemplate(templateResource.getStream())
            .autoCloseStream(true)
            .registerConverter(new ExcelBigNumberConvert())
            .build()) {

            for (int i = 0; i < data.size(); i++) {
                WriteSheet writeSheet = EasyExcel.writerSheet(i).build();
                FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();

                for (Map.Entry<String, Object> entry : data.get(i).entrySet()) {
                    if (entry.getValue() instanceof Collection) {
                        excelWriter.fill(new FillWrapper(entry.getKey(), (Collection<?>) entry.getValue()),
                            fillConfig, writeSheet);
                    } else {
                        excelWriter.fill(entry.getValue(), writeSheet);
                    }
                }
            }
        }
    }

    // --------------------- 响应设置 ---------------------

    private static void setExcelResponse(String sheetName, Context ctx) {
        String filename = encodingFilename(sheetName);
        String encodedFilename;
        try {
            encodedFilename = URLEncoder.encode(filename, StandardCharsets.UTF_8.toString());
        } catch (UnsupportedEncodingException e) {
            encodedFilename = filename.replaceAll("[^\\w]", "_");
        }

        ctx.headerSet("Content-Disposition", "attachment; filename=" + encodedFilename);
        ctx.contentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        ctx.charset("UTF-8");
    }

    // --------------------- 工具方法 ---------------------

    /**
     * 解析导出值 0=男,1=女,2=未知
     */
    public static String convertByExp(String propertyValue, String converterExp, String separator) {
        StringBuilder propertyString = new StringBuilder();
        String[] convertSource = converterExp.split(StringUtils.SEPARATOR);
        for (String item : convertSource) {
            String[] itemArray = item.split("=");
            if (StringUtils.containsAny(propertyValue, separator)) {
                for (String value : propertyValue.split(separator)) {
                    if (itemArray[0].equals(value)) {
                        propertyString.append(itemArray[1]).append(separator);
                        break;
                    }
                }
            } else {
                if (itemArray[0].equals(propertyValue)) {
                    return itemArray[1];
                }
            }
        }
        return StringUtils.stripEnd(propertyString.toString(), separator);
    }

    /**
     * 反向解析值 男=0,女=1,未知=2
     */
    public static String reverseByExp(String propertyValue, String converterExp, String separator) {
        StringBuilder propertyString = new StringBuilder();
        String[] convertSource = converterExp.split(StringUtils.SEPARATOR);
        for (String item : convertSource) {
            String[] itemArray = item.split("=");
            if (StringUtils.containsAny(propertyValue, separator)) {
                for (String value : propertyValue.split(separator)) {
                    if (itemArray[1].equals(value)) {
                        propertyString.append(itemArray[0]).append(separator);
                        break;
                    }
                }
            } else {
                if (itemArray[1].equals(propertyValue)) {
                    return itemArray[0];
                }
            }
        }
        return StringUtils.stripEnd(propertyString.toString(), separator);
    }

    /**
     * 编码文件名
     */
    public static String encodingFilename(String filename) {
        return IdUtil.fastSimpleUUID() + "_" + filename + ".xlsx";
    }
}